<template>
  <div class="page">
    <!-- 类型栏 -->
    <el-card class="type-bar">
      <el-button
        v-for="(item, idx) in selectData.typeList"
        :key="idx"
        type="primary"
        plain
        @click="add(item.value)"
        >{{ item.name }}</el-button
      >
    </el-card>
    <!-- 主体 -->
    <el-card class="main">
      <!-- 工具栏 -->
      <div class="tool-bar">
        <el-button @click="exportDialogShow = true">导出</el-button>
        <!-- <el-button @click="importDialogShow = true">导入</el-button> -->
        <el-button @click="clean">清空</el-button>
      </div>
      <!-- 表单项 -->
      <div
        v-for="(fItem, fIdx) in formList"
        :key="'main-' + fIdx"
        @click="formSelect(fIdx)"
      >
        <el-card
          class="main-card"
          :class="{ active: currentFormIndex == fIdx }"
        >
          <div slot="header" class="formHeader">
            <div class="formTitle">
              <span>表单名：</span>
              <el-input v-model="fItem.functionName" />
            </div>
            <div class="tools">
              <!-- 导入 -->
              <el-button
                type="text"
                icon="el-icon-document-add"
                @click="importShow(fIdx)"
              ></el-button>
              <!-- 删除 -->
              <el-button
                type="text"
                icon="el-icon-delete"
                @click="delForm()"
              ></el-button>
            </div>
          </div>
          <el-form :form="fItem.jsonForm" class="main-form">
            <draggable v-model="fItem.jsonList" :group="{ name: 'people' }">
              <transition-group>
                <div
                  v-for="(field, idx) in fItem.jsonList"
                  :key="idx"
                  class="input-items"
                  :class="{
                    active: currentFormIndex == fIdx && currentIndex == idx,
                    'dst-block': field.className == 'dst-block',
                    'is-required': field.commonParams[4].value,
                  }"
                  @click="handleSelectItem(field, idx, fIdx)"
                >
                  <div class="flex">
                    <div class="label">{{ field.label }}</div>
                    <div class="input">
                      <!-- 输入框 -->
                      <template v-if="field.type == 'input'">
                        <el-input v-model="jsonForm[field.prop]"></el-input>
                      </template>
                      <!-- 下拉框 -->
                      <template v-if="field.type == 'select'">
                        <el-select v-model="jsonForm[field.prop]"></el-select>
                      </template>
                      <!-- 单选框 -->
                      <template v-if="field.type == 'radio'">
                        <el-radio v-model="jsonForm[field.prop]"></el-radio>
                      </template>
                      <!-- 多选框 -->
                      <template v-if="field.type == 'checkbox'">
                        <el-checkbox
                          v-model="jsonForm[field.prop]"
                        ></el-checkbox>
                      </template>
                      <!-- 级联框 -->
                      <template v-if="field.type == 'cascader'">
                        <el-cascader
                          v-model="jsonForm[field.prop]"
                        ></el-cascader>
                      </template>
                      <!-- 日期 -->
                      <template v-if="field.type == 'date'">
                        <el-date-picker
                          v-model="jsonForm[field.prop]"
                          type="date"
                          placeholder="选择日期"
                        >
                        </el-date-picker>
                      </template>
                      <!-- 日期范围 -->
                      <template v-if="field.type == 'daterange'">
                        <el-date-picker
                          v-model="jsonForm[field.prop]"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                        >
                        </el-date-picker>
                      </template>
                      <!-- 文本域 -->
                      <template v-if="field.type == 'textarea'">
                        <el-input
                          v-model="jsonForm[field.prop]"
                          type="textarea"
                        ></el-input>
                      </template>
                      <!-- slot -->
                      <template v-if="field.type == 'slot'">
                        <div></div>
                      </template>
                      <!-- 上传 -->
                      <template v-if="field.type == 'dstUpload'">
                        <el-upload v-model="jsonForm[field.prop]" action="">
                          <el-button>上传</el-button>
                        </el-upload>
                      </template>
                    </div>
                  </div>
                  <!-- 操作栏 -->
                  <div v-show="currentIndex == idx" class="tools">
                    <i
                      class="el-icon-arrow-up"
                      @click.capture.stop="moveFn(idx, -1)"
                    ></i>
                    <i
                      class="el-icon-arrow-down"
                      @click.capture.stop="moveFn(idx, 1)"
                    ></i>
                    <i
                      class="el-icon-delete"
                      @click.capture.stop="delFn(fIdx, idx)"
                    ></i>
                  </div>
                </div>
              </transition-group>
            </draggable>
          </el-form>
        </el-card>
      </div>
      <el-button
        icon="el-icon-plus"
        type="info"
        circle
        @click="addFun"
      ></el-button>
    </el-card>
    <!-- 属性栏 -->
    <el-card class="property-bar">
      <template v-if="currentItem">
        <el-form :model="currentItem" label-width="120px" label-position="left">
          <el-tabs type="border-card">
            <el-tab-pane label="基础属性">
              <FormItem :list="currentItem.commonParams"></FormItem>
            </el-tab-pane>
            <el-tab-pane v-if="optionsProperty" label="选项属性">
              <FormItem :list="currentItem.selectProperty"></FormItem>
            </el-tab-pane>
            <el-tab-pane label="校验属性">
              <FormItem :list="currentItem.validateParams"></FormItem>
            </el-tab-pane>
            <el-tab-pane
              v-if="currentItem.type == 'dstUpload'"
              label="上传属性"
            >
              <FormItem :list="currentItem.uploadParams"></FormItem>
            </el-tab-pane>
          </el-tabs>
        </el-form>
      </template>
    </el-card>
    <!-- 导出弹窗 -->
    <el-dialog
      title="导出"
      width="500px"
      :visible="exportDialogShow"
      :before-close="handleCloseDialog"
    >
      <el-form :model="exportForm">
        <el-form-item label="输出目录">
          <el-input v-model="exportForm.dir" />
        </el-form-item>
        <el-form-item label="输出文件名">
          <el-input v-model="exportForm.fileName">
            <template slot="append">.js</template>
          </el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button class="" @click="handleCloseDialog()">取消</el-button>
        <el-button class="el-button--success" @click="exportFn">确定</el-button>
      </div>
    </el-dialog>
    <!-- 导入弹窗 -->
    <el-dialog
      title="导入"
      width="500px"
      :visible="importDialogShow"
      :before-close="handleCloseDialog"
    >
      <el-form :model="importForm">
        <el-form-item label="导入格式">
          <el-radio-group v-model="importForm.type">
            <el-radio label="yapi">yapi格式</el-radio>
            <!-- <el-radio label="dstForm">dstForm格式</el-radio> -->
          </el-radio-group>
        </el-form-item>
        <el-form-item label="导入内容">
          <el-input
            v-model="importForm.content"
            type="textarea"
            :rows="20"
          ></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button class="" @click="handleCloseDialog()">取消</el-button>
        <el-button class="el-button--success" @click="importJson"
          >确定</el-button
        >
      </div>
    </el-dialog>
  </div>
</template>

<script>
import cloneDeep from 'lodash/cloneDeep'
import draggable from 'vuedraggable'
import FormItem from '@/components/formItem'

import { check } from '@/utils/valid'

export default {
  name: 'FormGen',
  components: {
    FormItem,
    draggable,
  },
  data() {
    return {
      valid: {
        check,
      },
      // 表单模块
      formList: [
        {
          functionName: 'dataArr',
          jsonList: [], // 子表单
        },
      ],
      jsonForm: {},
      selectData: {
        //
        typeList: [
          { value: 'input', name: '输入框 input' },
          { value: 'textarea', name: '文本域 textarea' },
          { value: 'select', name: '选择框 select' },
          { value: 'radio', name: '单选框 radio' },
          { value: 'checkbox', name: '多选框 checkbox' },
          { value: 'cascader', name: '级联 cascader' },
          { value: 'date', name: '日期选择器 date' },
          { value: 'daterange', name: '日期范围选择器 daterange' },
          { value: 'dstUpload', name: '上传 dstUpload' },
          { value: 'slot', name: '插槽 slot' },
        ],
        // 校验方法
        validateList: [
          { name: '文本限制长度', value: 'checkTitleLenth' },
          { name: '下拉框', value: 'checkSelect' },
          { name: '限制数字区间', value: 'rangeNumber' },
          { name: '手机号', value: 'validatePhone' },
          { name: '手机号、电话带分机号', value: 'validatePhoneTel' },
          { name: '邮箱', value: 'validateEmail' },
          { name: '身份证', value: 'validateIdCard' },
        ],
        // 文件类型
        fileTypes: [
          { name: 'jpg', value: 'jpg' },
          { name: 'jpeg', value: 'jpeg' },
          { name: 'png', value: 'png' },
          { name: 'pdf', value: 'pdf' },
          { name: 'xlsx', value: 'xlsx' },
          { name: 'doc', value: 'doc' },
          { name: 'docx', value: 'docx' },
          { name: 'zip', value: 'zip' },
        ],
      },
      currentFormIndex: 0, // 当前表单字段
      currentIndex: null, // 当前字段index
      // 导出弹窗
      exportDialogShow: false,
      exportForm: {
        dir: '',
        fileName: '',
      },
      // 导入弹窗
      importDialogShow: false,
      importForm: {
        type: 'yapi',
        content: '',
      },
    }
  },
  computed: {
    // computed默认会将data传入
    currentForm({ currentFormIndex }) {
      return currentFormIndex !== null ? this.formList[currentFormIndex] : null
    },
    currentItem({ currentForm, currentIndex }) {
      console.log(currentForm.jsonList[currentIndex], 999999)
      return currentForm && currentIndex !== null
        ? currentForm.jsonList[currentIndex]
        : null
    },
    optionsProperty({ currentItem }) {
      return ['select', 'cascader'].includes(currentItem.type)
    },
  },
  methods: {
    add(typeName) {
      this.currentForm.jsonList.push(this.generateItem({ typeName }))
    },
    addFun() {
      this.formList.push({
        functionName: 'dataArr',
        jsonList: [],
      })
    },
    // 选中
    handleSelectItem(row, idx, fIdx) {
      this.currentFormIndex = fIdx
      this.currentIndex = idx
    },
    // 表单选中
    formSelect(fIdx) {
      if (this.currentFormIndex == fIdx) return
      this.currentFormIndex = fIdx
    },
    // 移动
    moveFn(index, move) {
      if (index == 0 && move == -1) {
        return
      }
      if (index == this.jsonList.length - 1 && move == 1) {
        return
      }
      const toIndex = index + move
      this.jsonList[toIndex] = this.jsonList.splice(
        index,
        1,
        this.jsonList[toIndex]
      )[0]
      this.currentIndex = toIndex
    },
    delFn(fIdx, idx) {
      this.$confirm('是否删除?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.formList[fIdx].jsonList.splice(idx, 1)
      })
    },
    /** 生成模板 **/
    genTemplate(formList) {
      let temp = ''
      formList.forEach((item) => {
        temp += this.genFormTemp(item.jsonList, item.functionName)
      })
      return temp
    },
    /** 生成模板 **/
    genFormTemp(schema, functionName) {
      // 校验函数引入
      let validatorFunctions = []
      let debounce = ''

      schema = schema.map((item) => {
        const params = {}
        this.formatCommon(item, params)
        // select类型
        if (['select', 'radio', 'checkbox', 'cascader'].includes(item.type)) {
          const isDebounce = this.formatSelect(item, params, debounce)
          // debugger;
          if (isDebounce && !debounce) {
            debounce = 'import debounce from \'lodash/debounce\''
          }
        }
        // 上传类型
        if (item.type == 'dstUpload') {
          this.formatDstUpload(item, params)
        }
        // 处理rules
        this.formatRules(item, validatorFunctions, params)
        return params
      })
      /*
       * ^javascript('代码段')$  用于生成之前包裹动态部分，生成时会将其解除
       * 例如：
       * ^javascript('${item.validator}()')$ -----> checkSelect()
       * */
      let tempString = JSON.stringify(schema, null, '\t')
      tempString = tempString.replace(/"\^javascript\('(.*)'\)\$"/g, '$1')
      validatorFunctions = validatorFunctions.length
        ? `import { ${validatorFunctions.join(
            ','
          )} } from '@/portal-common/utils/testedFunc/formValidate'`
        : ''
      return `
${validatorFunctions}
${debounce}
export function ${functionName}(){
  return ${tempString}
}`
    },
    // 导出
    exportFn() {
      console.log(this.formList, 99999)
      const schema = this.genTemplate(cloneDeep(this.formList))
      console.log(schema)
      if (this.$vscode) {
        try {
          this.$vscode.postMessage({
            cmd: 'genFile',
            params: {
              dir: this.exportForm.dir,
              fileName: this.exportForm.fileName + '.js',
              content: schema,
            },
          })
          this.exportDialogShow = false
        } catch (e) {
          this.$message.error(e)
        }
      } else {
        // 文件下载
        const local = window.location.hostname == 'localhost'
        if (local) {
          this.$message.info('调试模式不执行下载，控制台上查看输出结果')
        } else {
          const flieName = 'form.js'
          const link = document.createElement('a')
          link.download = flieName

          var blob = new Blob([schema])
          link.href = URL.createObjectURL(blob)

          document.body.appendChild(link)
          link.click()
          document.body.removeChild(link)
        }
      }
    },
    // 导入弹窗
    importShow(fIdx) {
      this.importDialogShow = true
      this.currentFormIndex = fIdx
      this.currentIndex = null
    },
    // 导入
    importJson() {
      const { type, content } = this.importForm
      const form = this.formList[this.currentFormIndex]
      if (type == 'yapi') {
        let properties = {}
        try {
          const obj = JSON.parse(content)
          properties = obj.properties
        } catch (e) {
          this.$message.error('解析失败')
          console.warn(e)
          return
        }

        form.jsonList = Object.keys(properties).map((prop) =>
          this.generateItem({
            typeName: 'input',
            prop: prop,
            name: properties[prop].description,
          })
        )
      } else {
        // 格式化json;
        let obj = []
        try {
          obj = this.transDstData(content)
        } catch (e) {
          this.$message.error('解析失败')
          console.warn(e)
          return
        }
        form.jsonList = obj
      }

      this.handleCloseDialog()
    },
    handleCloseDialog() {
      this.exportDialogShow = false
      this.importDialogShow = false
    },
    // 转译dst格式数据
    transDstData(dstData) {
      const string = dstData
        .replace(/\r|\n|\s/g, '')
        .replace(/validator:(.*\((.*)\))/g, 'validator:')
        .replace(/(\w*):/g, '$1:')
        .replace(/(,})/g, '}')
        .replace(/(,])/g, ']')
      console.log(string)
      const res = JSON.parse(dstData)
      return res
    },
    // 类型模板
    typesMap(typeName) {
      const selectProperty = [
        {
          prop: 'optionsDict',
          type: 'input',
          value: 'optionsDict',
          label: '选项字典',
        },
        {
          prop: 'propsLabel',
          type: 'input',
          value: 'name',
          label: '选项Label',
        },
        { prop: 'propsValue', type: 'input', value: 'id', label: '选项Value' },
        {
          prop: 'propsDetail',
          type: 'input',
          value: 'Name',
          label: '选项翻译',
        },
        {
          prop: 'remoteMethod',
          type: 'input',
          label: '远程搜索',
        },
      ]

      const result = {}
      switch (typeName) {
        case 'input':
        case 'textarea':
          result.validateParams = this.validateMap('checkTitleLenth')
          break
        case 'select':
        case 'radio':
        case 'checkbox':
          result.validateParams = this.validateMap('checkSelect')
          result.selectProperty = selectProperty
          break
        case 'cascader':
          result.validateParams = this.validateMap('checkSelect')
          result.selectProperty = selectProperty
          result.props = { children: 'childs' }
          break
        case 'dstUpload':
          result.validateParams = this.validateMap('checkSelect')
          result.uploadParams = [
            {
              prop: 'listType',
              label: '展示方式',
              value: 'text',
              type: 'radio',
              options: [
                { name: 'text', value: 'text' },
                { name: 'picture-card', value: 'picture-card' },
              ],
            },
            {
              prop: 'type',
              label: '文件类型',
              value: ['jpg', 'jpeg', 'png', 'pdf', 'xlsx'],
              type: 'multiSelect',
              options: this.selectData.fileTypes,
            },
            { prop: 'size', label: '文件大小(MB)', value: 50, type: 'input' },
            { prop: 'limit', label: '文件个数', value: 10, type: 'input' },
          ]
          break
        default:
          result.validateParams = this.validateMap('checkSelect')
          break
      }

      return result
    },
    // 校验属性模板
    validateMap(validatorName) {
      const validator = {
        prop: 'validator',
        label: '校验规则',
        value: validatorName,
        type: 'select',
        options: this.selectData.validateList,
        change: (e) => {
          this.currentItem.validateParams = this.validateMap(e)
        },
      }
      let result = []
      switch (validatorName) {
        case 'checkTitleLenth':
          result = [
            validator,
            { prop: 'max', label: '最大长度', value: 30, type: 'input' },
            { prop: 'min', label: '最小长度', value: 0, type: 'input' },
          ]
          break
        case 'rangeNumber':
          result = [
            validator,
            { prop: 'max', label: '最大长度', value: 30, type: 'input' },
            { prop: 'min', label: '最小长度', value: 0, type: 'input' },
            {
              prop: 'isIntegerNumber',
              label: '是否只支持整数',
              value: false,
              type: 'switch',
            },
          ]
          break
        default:
          result = [validator]
          break
      }
      return result
    },
    /** 生成字段
     *  @params typeName      表单类型
     *  @params name          表单名称
     *  @params prop          字段名
     *  @params validator     校验函数
     *  @params required      是否必填
     *
     */
    generateItem({ typeName, name, prop, required }) {
      const emp = typeName + Math.floor(100 * Math.random())
      const params = {
        type: typeName,
        label: name || emp,
        commonParams: [
          {
            label: '类型',
            type: 'select',
            prop: 'type',
            value: typeName,
            options: this.selectData.typeList,
            change: (e) => {
              params.type = e
              const { validateParams, selectProperty } = this.typesMap(e)
              params.validateParams = validateParams
              if (selectProperty) {
                params.selectProperty = selectProperty
              }
            },
          },
          {
            label: '字段名',
            type: 'input',
            prop: 'label',
            value: name || emp,
            change(e) {
              params.label = e
            },
          },
          { label: '字段', type: 'input', prop: 'prop', value: prop || emp },
          {
            label: '展示方式',
            type: 'radio',
            prop: 'className',
            value: null,
            options: [
              { name: '两列式', value: null },
              { name: '一列式', value: 'dst-block' },
            ],
          },
          {
            label: '是否必填',
            type: 'switch',
            prop: 'required',
            value: required || true,
          },
        ],
        ...this.typesMap(typeName),
      }
      return params
    },
    // 生成模板-通用属性
    formatCommon(item, params) {
      item.commonParams.forEach((item) => {
        params[item.prop] = item.value
      })

      // !params.className && delete params.className;
      return item
    },
    // 生成模板-选择属性
    formatSelect(item, params) {
      const p = {}
      let isDebounce = false
      item.selectProperty.forEach((item) => {
        p[item.prop] = item.value
      })
      params.options = `^javascript('this.selectData.${String(
        p.optionsDict
      )} || []')$`
      if (p.remoteMethod) {
        params.remote = true
        params.remoteMethod = `^javascript('debounce(this.${String(
          p.remoteMethod
        )}, 300)')$`
        isDebounce = true
      }
      const commonProps = {
        label: p.propsLabel,
        value: p.propsValue,
        detailProp: p.propsDetail,
      }
      params.props = Object.assign({}, item.props, commonProps)

      return isDebounce
    },
    // 生成模板-处理dstUpload
    formatDstUpload(item) {
      const params = {}
      item.uploadParams &&
        item.uploadParams.forEach((item) => {
          params[item.prop] = item.value
        })
      item.listType = params.listType
      item.limit = params.limit
      item.filterOpts = { type: params.type.join(','), size: params.size }
      return item
    },
    // 生成模板-处理rules
    formatRules(item, validatorFunctions, params) {
      const p = {
        required: params.required,
      }
      item.validateParams.forEach((item) => {
        p[item.prop] = item.value
      })
      const validator = p.validator

      delete p.validator
      delete params.required
      const validateParams = JSON.stringify(p)
        .replace(/"/g, '')
        .replace(/\s+/g, '')

      params.rules = {
        required: p.required || false,
        trigger: 'change',
        validator: `^javascript('${validator}(${validateParams})')$`,
      }

      // 添加校验引入
      if (validator) {
        if (validatorFunctions.indexOf(validator) == -1) {
          validatorFunctions.push(validator)
        }
      }
      return item
    },
    // 生成模板-删除多余字段
    excludeFields(item) {
      delete item.optionsDict
      delete item.propsLabel
      delete item.propsValue
      delete item.propsDetail
      delete item.required

      delete item.commonParams
      delete item.validateParams
      delete item.uploadParams
      !item.className && delete item.className
    },
    // 清空
    clean() {
      this.formList = [
        {
          functionName: 'dataArr',
          jsonList: [], // 子表单
        },
      ]
    },
    delForm(fIdx) {
      this.$confirm('是否删除?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      }).then(() => {
        this.formList.splice(fIdx, 1)
      })
    },
  },
}
</script>

<style lang="scss" scoped>
.flex {
  display: flex;
}
.page {
  display: flex;
  width: 100%;
  margin-top: 20px;
  min-width: 1400px;
  height: calc(100% - 110px);
  & > div {
    margin: 0 5px;
  }
}
.type-bar {
  padding-top: 20px;
  width: 300px;
  // height: 100%;
  display: flex;
  flex-wrap: wrap;
  align-content: start;
  ::v-deep .el-card__body {
    padding: 0px;
  }
  .el-button {
    margin-left: 5px;
    margin-right: 5px;
    margin-bottom: 20px;
  }
}

.main {
  flex: 1;
  height: 100%;
  overflow-y: auto;
  .tool-bar {
    margin-bottom: 10px;
  }
  .main-form {
    // display: flex;
    // flex-wrap: wrap;
    text-align: left;
  }
  .main-card {
    margin-bottom: 15px;
    &.active {
      border-color: #66b1ff;
    }
    ::v-deep .el-card__body {
      padding: 10px;
    }
  }
}

.property-bar {
  width: 350px;
  height: 100%;
  overflow-y: auto;
  ::v-deep .el-card__body {
    padding: 0px;
  }
}
.input-items {
  display: inline-block;
  padding: 5px 5px 5px 14px;
  border: 1px dashed #bbb;
  margin: 0 2% 10px;
  position: relative;
  box-sizing: border-box;
  width: 44%;
  &.is-required::before {
    content: "*";
    color: #f56c6c;
    margin-left: -7px;
    position: absolute;
  }
  &.active {
    border: 1px solid rgb(64, 158, 255);
  }
  .label {
    line-height: 40px;
    margin-right: 10px;
  }
  .tools {
    position: absolute;
    right: 0;
    bottom: 0;
    color: #fff;
    background: rgb(64, 158, 255);
    > i {
      width: 20px;
      height: 20px;
      line-height: 20px;
      cursor: pointer;
    }
  }
  &.dst-block {
    width: 100%;
  }
}
.formHeader {
  display: flex;
  justify-content: space-between;
}
.formTitle {
  width: 200px;
  display: flex;
  justify-content: center;
  > span {
    display: inline-block;
    width: 120px;
    line-height: 40px;
  }
}
</style>
